import time from '@knno/time';
import { DICT } from '../../dict/dict';
import {
	makeDayRangePickOptions,
	makeHourRangePickOptions,
	makeHourRangePickOptionsWithDate,
	makeMinuteRangePickOptions,
	makeMinuteRangePickOptionsWithDate,
	makeMonthRangePickOptions,
	makeSecondRangePickOptions,
	makeSecondRangePickOptionsWithDate,
	makeYearRangePickOptions,
} from '../../tools/datePickerOptions';
import { Calendar } from '../calendar/calendar';
import { PickerBox, type PickGroup, type PickOptions } from '../picker/picker';
import { isMobile, Selector } from '../selector/selector';
import { DATA } from '../symbol';
import { Toast } from '../toast/toast';
import { datePickerStyle } from './datePicker.css';

export type DateMode = 'dy' | 'dm' | 'dd' | 'th' | 'tm' | 'ts' | 'dd+th' | 'dd+tm' | 'dd+ts';

const FORMAT = {
	dy: 'yyyy',
	dm: 'yyyy-MM',
	dd: 'yyyy-MM-dd',
	th: 'HH',
	tm: 'HH:mm',
	ts: 'HH:mm:ss',
	'dd+th': 'yyyy-MM-dd HH',
	'dd+tm': 'yyyy-MM-dd HH:mm',
	'dd+ts': 'yyyy-MM-dd HH:mm:ss',
};

function makePicker(
	title: string,
	date: Date[],
	isRange: boolean,
	mode: DateMode,
	minDate: Date | undefined,
	maxDate: Date | undefined,
): PickObject {
	// use picker
	const dateOptions: PickOptions[] = [];
	const fmt = FORMAT[mode];
	if (/^d/.test(mode)) {
		// dateOptions.push(getYears());
		dateOptions.push(makeYearRangePickOptions(minDate, maxDate));
	}
	if (/d[dm]/.test(mode)) {
		// dateOptions.push(getMonths());
		dateOptions.push(makeMonthRangePickOptions(minDate, maxDate));
	}
	if (/dd/.test(mode)) {
		// dateOptions.push(getDays); // dynamically get days
		dateOptions.push(makeDayRangePickOptions(minDate, maxDate));
	}
	if (/t/.test(mode)) {
		// dateOptions.push(getHours());
		if (/^dd\+/.test(mode)) {
			dateOptions.push(makeHourRangePickOptionsWithDate(minDate, maxDate));
		} else {
			dateOptions.push(makeHourRangePickOptions(minDate, maxDate));
		}
	}
	if (/t[ms]/.test(mode)) {
		// dateOptions.push(getMinOrSec());
		if (/^dd\+/.test(mode)) {
			dateOptions.push(makeMinuteRangePickOptionsWithDate(minDate, maxDate));
		} else {
			dateOptions.push(makeMinuteRangePickOptions(minDate, maxDate));
		}
	}
	if (/s/.test(mode)) {
		// dateOptions.push(getMinOrSec());
		if (/^dd\+/.test(mode)) {
			dateOptions.push(makeSecondRangePickOptionsWithDate(minDate, maxDate));
		} else {
			dateOptions.push(makeSecondRangePickOptions(minDate, maxDate));
		}
	}
	const groups: PickGroup[] = [
		{
			name: DICT.from,
			parts: dateOptions,
		},
	];
	if (isRange) {
		groups.push({
			name: DICT.to,
			parts: dateOptions,
		});
	}
	let pickHandler: PickHandler;
	const pk = new PickerBox({
		class: 'popup',
		groups,
		onChange: () => {
			if (isRange) {
				const gid = pk.activeGroup;
				const values = getDateValue();
				if (gid === 0) {
					if (values[1] < values[0]) {
						values[1] = values[0];
						pk[DATA].selection = values.map((d) => {
							return time.format(d, fmt)!.split(/[-: ]/g);
						});
					}
				} else if (gid === 1) {
					if (values[0] > values[1]) {
						values[0] = values[1];
						pk[DATA].selection = values.map((d) => {
							return time.format(d, fmt)!.split(/[-: ]/g);
						});
					}
				}
			}
		},
	});
	for (let i = 2 - date.length; i > 0; i--) {
		date.push(new Date());
	}
	pk.setValue(
		date.map((d) => {
			return time.format(d, fmt)!.split(/[-: ]/g);
		}),
	);
	function getDateValue() {
		const values = pk.value;
		const value = [time.parse(values[0].join(' '), fmt.replaceAll(/[-:]/g, ' '))!];
		if (isRange) {
			value.push(time.parse(values[1].join(' '), fmt.replaceAll(/[-:]/g, ' '))!);
		}
		return value;
	}
	function doPick() {
		const values = getDateValue();
		if (isRange) {
			pickHandler(values);
		} else {
			pickHandler(values[0]);
		}
	}
	return {
		type: 'picker',
		title,
		container: (<div class={datePickerStyle}>{pk.el}</div>) as HTMLDivElement,
		select() {
			doPick();
		},
		focus() {
			pk.focus();
		},
		onSelect(handler) {
			pickHandler = handler;
		},
	};
}

function makeCalendar(
	title: string,
	date: Date[],
	isRange: boolean,
	mode: DateMode,
	minDate: Date | undefined,
	maxDate: Date | undefined,
): PickObject {
	const cal = new Calendar({
		range: isRange,
		value: date,
		onPick: !isRange ? () => doPick() : undefined,
		min: minDate,
		max: maxDate,
	});
	if (mode === 'dy') {
		cal.setMode('year');
	} else if (mode === 'dm') {
		cal.setMode('month');
	} else {
		cal.setMode('date');
	}
	let pickHandler: PickHandler;
	function doPick() {
		const val = cal.value;
		if (val === null) {
			Toast.show(DICT.notChoosen);
			return;
		}
		pickHandler(val);
	}
	return {
		type: 'calendar',
		title,
		container: (<div class={datePickerStyle}>{cal.el}</div>) as HTMLDivElement,
		select() {
			// do pick;
			doPick();
		},
		focus() {
			cal.focus();
		},
		onSelect(handler) {
			pickHandler = handler;
		},
	};
}

function makeContainer(
	date: Date[],
	isRange: boolean,
	mode: DateMode,
	minDate: Date | undefined,
	maxDate: Date | undefined,
): PickObject {
	const isTime = /t/.test(mode);
	const useActionSheet = isMobile();
	let title: string;
	if (isTime) {
		if (isRange) {
			title = DICT.chooseTimeRange;
		} else {
			title = DICT.chooseTime;
		}
	} else {
		if (isRange) {
			title = DICT.chooseDateRange;
		} else {
			title = DICT.chooseDate;
		}
	}
	if (useActionSheet || isTime) {
		return makePicker(title, date, isRange, mode, minDate, maxDate);
	} else {
		return makeCalendar(title, date, isRange, mode, minDate, maxDate);
	}
}

type PickHandler = (value: Date | Date[]) => void;

interface PickObject {
	container: HTMLElement;
	title: string;
	type: 'calendar' | 'picker';
	select: () => void;
	focus: () => void;
	onSelect: (handler: PickHandler) => void;
}

export interface DatePickHandler {
	onSelect: (callback: PickHandler) => this;
	onClose: (callback: () => void) => this;
	close: () => void;
}

type OpenSelectOptions = {
	/**
	 * Which node should the float window will align to it, if not provide refer than forcely use action sheet as the operation UI instead of float window.
	 */
	refer?: HTMLElement;
	/**
	 * default value
	 */
	date?: Date[];
	/**
	 * Indicate whether it's a choosen of the time range, default is false
	 */
	range?: boolean;
	/**
	 * default is dd
	 */
	mode?: DateMode;
	focus?: boolean;
	focusOnSelect?: HTMLElement;
	minDate?: Date;
	maxDate?: Date;
};

export const DatePicker = {
	parseDate(date: string | null, mode: DateMode): Date | null {
		if (!date) {
			return null;
		}
		const fmt = FORMAT[mode];
		let t: Date | null;
		t = time.parse(date, fmt);
		if (!t) {
			t = time.parse(date) ?? null;
		}
		return t;
	},

	trimDateString(date: string | null, mode: DateMode): string | null {
		const fmt = FORMAT[mode];
		const t = DatePicker.parseDate(date, mode);
		return t ? time.format(t, fmt) : null;
	},

	formatDate<T extends Date | null, R extends T extends Date ? string : null>(
		date: T,
		mode: DateMode,
	): R {
		if (date instanceof Date) {
			const fmt = FORMAT[mode];
			return time.format(date, fmt) as R;
		} else {
			return null as R;
		}
	},

	show({
		refer,
		date = [],
		range = false,
		mode = 'dd',
		focus = true,
		focusOnSelect,
		minDate,
		maxDate,
	}: OpenSelectOptions = {}): DatePickHandler {
		let onSelect: PickHandler | undefined;
		let onClose: (() => void) | undefined;
		const pickHandler = makeContainer(date, range, mode, minDate, maxDate);
		pickHandler.onSelect((value) => {
			if (Array.isArray(value)) {
				value.sort((a, b) => a.getTime() - b.getTime());
			}
			onSelect?.(value);
			pop.close();
			focusOnSelect?.focus();
		});
		const pop = Selector.show(pickHandler.container, {
			refer,
			extraClass: 'no-h-padding no-t-padding',
			useAction: true,
			focus: false,
			width: 'auto',
			noActionBar: pickHandler.type === 'calendar' && !range ? true : false,
			title: pickHandler.title,
		})
			.onOpen(() => {
				if (focus) pickHandler.focus();
			})
			.onClose(() => {
				onClose?.();
			})
			.onOK(() => {
				pickHandler.select();
			});
		return {
			close() {
				pop.close();
			},
			onClose(callback) {
				onClose = callback;
				return this;
			},
			onSelect(callback) {
				onSelect = callback;
				return this;
			},
		};
	},
};
